/*
 * Copyright (c) 1989, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
#if 0
static char sccsid[] = "@(#)display.c	8.1 (Berkeley) 6/6/93";
#endif
#endif /* not lint */

#include <sys/param.h>
#include <sys/stat.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#if DEBUG > 1
#include <inttypes.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "hexdump.h"

enum _vflag vflag = FIRST;

static off_t address = 0;		/* address/offset in stream */
static off_t eaddress = 0;		/* end address */

static void print(PR *, char *);

void
display(void)
{
	FS *fs;
	FU *fu;
	PR *pr;
	int cnt;
	off_t saveaddress;
	char *bp, savech, *savebp;

	savech = 0;

	/* 
	 * XXX (gcooper): This loop seems largely overly engineered with all of
	 * the saving/restoring, but I need to determine whether or not it's
	 * just not something I understand yet because I haven't digested
	 * what's supposed to occur here enough.
	 */
	while ((bp = get()) != NULL) {

#if DEBUG > 1
		fprintf(stderr, "%s (outer): '%s'\n", __func__, bp);
#endif

		for (fs = fshead, savebp = bp, saveaddress = address;
		    fs != NULL;
		    fs = fs->nextfs, bp = savebp, address = saveaddress) {

			for (fu = fs->nextfu;
			    fu != NULL && !(fu->flags & F_IGNORE);
			    fu = fu->nextfu) {
				for (cnt = fu->reps; 0 < cnt; cnt--) {

					for (pr = fu->nextpr;
					    pr != NULL;
					    address += pr->bcnt, bp += pr->bcnt,
					    pr = pr->nextpr) {

#if DEBUG > 1
						fprintf(stderr,
						    "\n%s: (inner): '%s', "
						    "address: %" PRIu64 ", "
						    "eaddress: %" PRIu64 "\n",
						    __func__, bp, address,
						    eaddress);
#endif
						if (0 < eaddress &&
						    address >= eaddress &&
						    !(pr->flags &
						        (F_TEXT | F_BPAD)))
							bpad(pr);
						if (cnt == 1 && pr->nospace) {
							savech = *pr->nospace;
							*pr->nospace = '\0';
						}
						print(pr, bp);
						if (cnt == 1 && pr->nospace)
							*pr->nospace = savech;

					}

				}

		    	}

		}

	}
	/* 
	 * The end-user wants to print out a format unit with "%_A*" in it,
	 * so iterate through the end format unit printing out all of the
	 * format strings according to user specified format if eaddress or
	 * address is non-zero.
	 */
	if (endfu != NULL) {
		/* 
		 * The end address wasn't set, but the address was -- follow
		 * the confusing red bouncing ball in get() as that's the only
		 * area that eaddress is set, and address is incremented based
		 * on all of the return statements in the function if data is
		 * consumed.
		 */
		if (eaddress == 0 && address != 0)
			eaddress = address;
		if (eaddress != 0) {

			for (pr = endfu->nextpr; pr != NULL; pr = pr->nextpr) {
				switch(pr->flags) {
				case F_ADDRESS:
					(void)printf(pr->fmt,
					    (quad_t)eaddress);
					break;
				case F_TEXT:
					(void)printf("%s", pr->fmt);
					break;
				/* XXX (gcooper): should default error out? */
				}

			}

		}

	}

}

static void
print(PR *pr, char *bp)
{
	long double	ldbl;
	double		f8;
	float		f4;
	int8_t		s8;
	int16_t		s2;
	int32_t		s4;
	uint16_t	u2;
	uint32_t	u4;
	uint64_t	u8;

#if DEBUG > 1
	fprintf(stderr, "%s: flags: %u, fmt: '%s'\n",
	    __func__, pr->flags, pr->fmt);
#endif

	switch(pr->flags) {
	case F_ADDRESS:
		(void)printf(pr->fmt, (quad_t)address);
		break;
	case F_BPAD:
#if DEBUG > 1
		fprintf(stderr, "%s: hit bpad\n", __func__);
#endif
		(void)printf(pr->fmt, "");
		break;
	case F_C:
		conv_c(pr, bp, eaddress != 0 ? eaddress - address :
		    ((off_t) blocksize - address) % (off_t) blocksize);
		break;
	case F_CHAR:
		(void)printf(pr->fmt, *bp);
		break;
	case F_DBL:
		switch(pr->bcnt) {
		case sizeof(f4):
			memcpy(&f4, bp, sizeof(f4));
			(void)printf(pr->fmt, f4);
			break;
		case sizeof(f8):
			memcpy(&f8, bp, sizeof(f8));
			(void)printf(pr->fmt, f8);
			break;
		default:
			if (pr->bcnt == sizeof(long double)) {
				memcpy(&ldbl, bp, sizeof(ldbl));
				(void)printf(pr->fmt, ldbl);
			}
			break;
		}
		break;
	case F_INT:
		switch(pr->bcnt) {
		case sizeof(signed char):
			(void)printf(pr->fmt, (quad_t)(signed char)*bp);
			break;
		case sizeof(s2):
			memcpy(&s2, bp, sizeof(s2));
			(void)printf(pr->fmt, (quad_t)s2);
			break;
		case sizeof(s4):
			memcpy(&s4, bp, sizeof(s4));
			(void)printf(pr->fmt, (quad_t)s4);
			break;
		case 8:
			memcpy(&s8, bp, sizeof(s8));
			(void)printf(pr->fmt, s8);
			break;
		}
		break;
	case F_P:
		(void)printf(pr->fmt, isprint(*bp) ? *bp : '.');
		break;
	case F_STR:
		(void)printf(pr->fmt, (char *)bp);
		break;
	case F_TEXT:
		(void)printf("%s", pr->fmt);
		break;
	case F_U:
		conv_u(pr, bp);
		break;
	case F_UINT:
		switch(pr->bcnt) {
		case 1:
			(void)printf(pr->fmt, (u_quad_t)*bp);
			break;
		case sizeof(u2):
			memcpy(&u2, bp, sizeof(u2));
			(void)printf(pr->fmt, (u_quad_t)u2);
			break;
		case sizeof(u4):
			memcpy(&u4, bp, sizeof(u4));
			(void)printf(pr->fmt, (u_quad_t)u4);
			break;
		case sizeof(u8):
			memcpy(&u8, bp, sizeof(u8));
			(void)printf(pr->fmt, u8);
			break;
		}
		break;
	}
	fflush(stdout);
}

void
bpad(PR *pr)
{
	static char const *spec = " -0+#";
	char *p1, *p2;

	/*
	 * Remove all conversion flags; '-' is the only one valid
	 * with %s, and it's not useful here.
	 */
	pr->flags = F_BPAD;
	/* Print out spaces with the appropriate width. */
	pr->cchar[0] = 's';
	pr->cchar[1] = '\0';
	for (p1 = pr->fmt; *p1 != '%'; p1++) ;
	/*
	 * We should never hit this case [this is a bug in hexdump(1)], but
	 * just to be sure we have meaningful data to capture (instead of
	 * screwing up the heap data in pr->fmt), let's assert that this isn't
	 * '\0' here.
	 */
	assert(*p1 != '\0');
	for (p2 = ++p1; *p1 != '\0' && strchr(spec, *p1) != NULL; p1++) ;
	/* Copy the contents of p1 into p2. */
	while ((*p2++ = *p1++) != '\0') ;

}

static char **_argv;

char *
get(void)
{
	static char ateof = 1;
	static char *curp = NULL;
	static char *savp = NULL;
	int valid_save;
	size_t eread, n, need, nread;

	if (curp == NULL) {
		if ((curp = calloc(sizeof(char), blocksize+1)) == NULL)
			err(EXIT_FAILURE, "%s:%d", __FILE__, __LINE__);
		if ((savp = calloc(sizeof(char), blocksize+1)) == NULL)
			err(EXIT_FAILURE, "%s:%d", __FILE__, __LINE__);
		valid_save = 0;
	} else {
		char *tmpp;
		/* Swap curp and savp */
		tmpp = curp;
		curp = savp;
		savp = tmpp;
		address += blocksize;
		valid_save = 1;
	}
#if DEBUG > 1
	fprintf(stderr, "%s: blocksize - %lu\n", __func__, blocksize);
#endif
	for (need = blocksize, nread = 0; ; ) {

#if DEBUG > 1
		fprintf(stderr, "ateof: %d, nread: %"PRIu64", need: %"
		    PRIu64"\n", ateof, nread, need);
#endif

		/*
		 * if read the right number of bytes, or at EOF for one file,
		 * and no other files are available, zero-pad the rest of the
		 * block and set the end flag.
		 */
		if (length == 0 || (ateof != 0 && next((char **)NULL) == 0)) {

			if (odmode && address < skip)
				errx(EXIT_FAILURE,
				    "cannot skip past end of input");
			if (need == blocksize)
				return ((char *)NULL);

			eaddress = address + nread;
			if (vflag != ALL && valid_save != 0 &&
			    memcmp(curp, savp, nread) == 0) {
				if (vflag != DUP)
					(void)printf("*\n");
				return ((char *)NULL);
			}
			memset((char *)curp + nread, 0, need);
			return (curp);

		}
		eread = length == -1 ? need : MIN((size_t) length, need);
		n = fread((char *)curp + nread, sizeof(char), eread, stdin);
#if DEBUG > 1
		fprintf(stderr, "%s: expecting %lu chars; got: %lu; "
		    "curp = %s\n", __func__, eread, n, curp);
#endif
		/* 
		 * Even though errors can occur, we cannot be sure that an EOF
		 * has been reached until 0 characters have been read in
		 * succession.
		 *
		 * Thus the check needs to remain at 0 or the code will
		 * regress.
		 *
		 * XXX (gcooper): why set ateof when n == 0 instead of when
		 * feof(3) is true?
		 */
		if (n == 0) {
			ateof = 1;
			if (ferror(stdin)) {
				warn("%s", _argv[-1]);
				clearerr(stdin);
			}
		} else {
			ateof = 0;
			if (length != -1)
				length -= n;
			need -= n;
			if (need == 0) {
				if (vflag == ALL || vflag == FIRST ||
				    valid_save == 0 ||
				    memcmp(curp, savp, blocksize) != 0) {
					if (vflag == DUP || vflag == FIRST)
						vflag = WAIT;
					return (curp);
				}
				if (vflag == WAIT)
					(void)printf("*\n");
				vflag = DUP;
				/*
				 * XXX (gcooper): it'd be nice if this was
				 * integrated into the loop next-iteration
				 * statement.
				 */
				address += blocksize;
				need = blocksize;
			} else
				nread += n;

		}

	}

}

size_t
peek(char *buf, size_t nbytes)
{
	size_t n, nread;
	int c;
	if (length != -1 && nbytes > (size_t)length)
		nbytes = length;
	nread = 0;
	while (nread < nbytes && (c = getc(stdin)) != EOF) {
		*buf++ = c;
		nread++;
	}
#if DEBUG > 1
	warnx("%s: %"PRIu64", %"PRIu64"", __func__, nbytes, nread);
#endif
	n = nread;
	while (n-- > 0) {
		c = *--buf;
		ungetc(c, stdin);
	}
#if DEBUG > 1
	warnx("%s: %"PRIu64"", __func__, n);
#endif
	return (nread);
}

int
next(char **argv)
{
	static int done = 0;
	int statok;

	if (argv != NULL)
		_argv = argv;
	else {

		do {

			if (*_argv != NULL) {

				done = 1;

				if (freopen(*_argv, "r", stdin) == NULL) {
					warn("%s", *_argv);
					exitval = 1;
					_argv++;
					continue;
				}

				statok = 1;

			} else {
				if (done++)
					return (0);
				statok = 0;
			}
			if (skip)
				doskip(statok ? *_argv : "stdin", statok);
			if (*_argv != NULL)
				_argv++;

		} while (skip);

	}

	return (1);

}

void
doskip(const char *fname, int statok)
{
	struct stat sb;
	int cnt;

	if (statok) {

		if (fstat(fileno(stdin), &sb) != 0)
			err(EXIT_FAILURE, "%s", fname);
		if (S_ISREG(sb.st_mode)) {
			if (skip >= sb.st_size) {
				address += sb.st_size;
				skip -= sb.st_size;
			} else {
				if (fseeko(stdin, skip, SEEK_SET))
					err(EXIT_FAILURE, "fseeko %s", fname);
				address += skip;
				skip = 0;
			}
			return;
		}

	}

	for (cnt = 0; cnt < skip && getc(stdin) != EOF; cnt++) ;

	address += cnt;
	skip -= cnt;
}
